/** @file    VGettyAMImpl.cpp
 *  @author  Alessandro Polo
 *  @version $Id: VGettyAMImpl.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the VGettyAMImpl class.
 * The header for this class can be found in VGettyAMImpl.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "VGettyAMImpl.h"

 #include <core/FileSystem.h>
 #include <algorithm> // folder parsing (sorting)

using namespace wosh;
using namespace wosh::communication;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

VGettyAMImpl::VGettyAMImpl( wosh::BundleGeneric& bundle ) : wosh::BundleGenericWorker(bundle)
 {
	this->deviceListener = NULL;

	this->vgettyEnabled = false;

	this->processID = "";
	this->dialScript = "";
	this->ctrlScript = "";

	this->bindAddressSlave = "";
	this->bindAddressDevice = "";

	this->folderMessageIncoming = "";
	this->folderMessageStored = "";

	this->messageGreeting = "greeting.rmd";

	this->bindAddressSlave = "192.168.0.2:7902";
	this->bindAddressDevice = "0.0.0.0:7901";
	this->folderMessageIncoming = "/var/spool/voice/messages";
	this->folderMessageStored = "/var/spool/voice/incoming";
	this->dialScript = "/usr/sbin/vgetty-dial.sh";
	this->ctrlScript = "/usr/sbin/vgetty-ctrl.sh";

	this->activeCalls.push_back( new VoiceCall() );

	this->messageGreeting = "";

	this->udpSocket = new wosh::network::SocketUdp();
	this->udpSocket->setIgnoreSelfDatagrams(true);
	this->udpSocket->setListener(this);
	this->udpSocket->setAddressPort(this->bindAddressDevice);
 }

VGettyAMImpl::~VGettyAMImpl() {
	Log(LOG_VERBOSE, ":~VGettyAMImpl(): Destroying.." );
	if ( isThreadRunning() ) {
		quitThread(10000);
	 }

	this->udpSocket->setListener(NULL);
	delete this->udpSocket; this->udpSocket = NULL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN

void VGettyAMImpl::runThread()
 {
	Log(LOG_VERBOSE, ":listen() Binding UDP socket to '%s'..", this->udpSocket->getAddressPort().c_str() );
	WRESULT ret = this->udpSocket->bindSocket();
	if ( WFAILED(ret) ) {
		Log(LOG_CRITICAL, ":listen() ERROR#%d Binding socket [%d] to %s", ret, this->udpSocket->getAddressPort().c_str(), this->udpSocket->isBinded() );
	 }

	while( this->running ) {
		ThreadImpl::sleepForSec(1);
		setThreadAlive();
	 }
	this->running = false;
 }

////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// UDP EVENTs

void VGettyAMImpl::udpIncomingDatagram( const char * data, int maxSize, const std::string& source, wosh::network::SocketUdp* )
 {
 	std::string msgdata(data);
	Log(LOG_VERBOSE, ":udpIncomingDatagram() Incoming Datagram: '%s'", data );

	if ( msgdata.find("INCOMING_CALL") == 0 ) {	 // callid doesn't work on my modem
		std::string phone = msgdata.substr(13);
		this->activeCalls.transactionBeginWrite();
		VoiceCall* call = this->activeCalls.front();
		if ( call != NULL ) {
			call->setSender_ID( phone );
			call->setIncomingCall();
			call->setRinging();
		 }
		this->activeCalls.transactionEnd();

		if ( this->deviceListener != NULL )
			this->deviceListener->phone_event( 0, 1, this );
	 }
	else if ( msgdata == "HANGUP_CALL" ) {
		this->activeCalls.transactionBeginWrite();
		VoiceCall* call = this->activeCalls.front();
		if ( call != NULL ) {
			call->setSender_ID("");
			call->setIdle();
			call->setCallType( VoiceCall::CALL_UNKNOWN_TYPE );
		 }
		this->activeCalls.transactionEnd();

		if ( this->deviceListener != NULL )
			this->deviceListener->phone_event( 0, 0, this );
	 }
	else if ( msgdata.find("DTMF") == 0 ) {
		this->activeCalls.transactionBeginWrite();
		VoiceCall* call = this->activeCalls.front();
		if ( call != NULL ) {
			call->setSender_ID("");
			call->setIdle();
			call->setCallType( VoiceCall::CALL_UNKNOWN_TYPE );
		 }
		this->activeCalls.transactionEnd();

		std::string sequence = msgdata.substr(4);
		if ( this->deviceListener != NULL )
			this->deviceListener->incoming_DTMF( 0, sequence, this );
	 }
	else if ( msgdata.find("NEW_MESSAGE") == 0 ) {

reloadMessageIncoming();

		if ( this->deviceListener != NULL )
			this->deviceListener->incoming_Message( 0, NULL, this );
		return;
	 }
	else {
		// unknown
	 }

/*
	- RAISE FACT(s)
	- RELOAD MESSAGESS
    - DTMF SEQUENCE
    - INTERACTIVE WITH CALLs (phone interface)
*/
 }

////////////////////////////////////////////////////////////////////////////////////////////////// UDP EVENTs
////////////////////////////////////////////////////////////////////////////////////////////////////// EVENTS

////////////////////////////////////////////////////////////////////////////////////////////////////// EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool VGettyAMImpl::isEnabled() const
 {
	std::string execStr = this->ctrlScript + " isenabled";
	int ret = system( execStr.c_str() );
	if ( ret == 0 ) {
		this->processID = "PROCESS NOT FOUND!";
		this->vgettyEnabled = false;
		Log(LOG_WARNING, ":isEnabled() : Process 'vgetty' NOT found [%s=>%d]", execStr.c_str(), ret );
		return false;
	 }
	else {
		this->processID = Utilities::toString<long>(ret) + "PROCESS 'vgetty' FOUND";
		this->vgettyEnabled = true;
		return true;
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT VGettyAMImpl::controlVgettyProcess( const std::string& command )
 {
	std::string execStr = this->ctrlScript + " " + command;
	int ret = system( execStr.c_str() );
	Log(LOG_VERBOSE, ":controlVgettyProcess() : Executed '%s' returned %d", execStr.c_str(), ret );

	if ( ret == 0 )
		return WRET_OK;
	return WRET_ERR_INTERNAL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT VGettyAMImpl::turnOn() {
	return controlVgettyProcess( "enable" );
 }

WRESULT VGettyAMImpl::turnOff() {
	return controlVgettyProcess( "disable" );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////// MESSAGES MANAGEMENT

//WRESULT VGettyAMImpl::deleteMessageIncoming( const std::string& message_file )

WRESULT VGettyAMImpl::reloadMessageIncoming()
 {
	WRESULT ret = reloadMessagesFromFolder( this->folderMessageIncoming, this->messagesIncoming );
	if ( ret != WRET_OK )
		return ret;

	return WRET_OK;
 }

WRESULT VGettyAMImpl::reloadMessageStored()
 {
	WRESULT ret = reloadMessagesFromFolder( this->folderMessageStored, this->messagesStored );
	if ( ret != WRET_OK )
		return ret;

	return WRET_OK;
 }

WRESULT VGettyAMImpl::reloadMessagesFromFolder( const std::string& folder, tAudioMessageList& msglist )
 {
	std::vector<std::string> files;
	std::string localPath = folder;
	FilePath::toLocalPath(localPath);
	WRESULT ret = FileSystem::getFilesOf(localPath, files, false, true ); // NOT recursive
	if ( WFAILED(ret) || files.size() == 0 ) {
		Log(LOG_CRITICAL, ":reloadMessagesFromFolder(%s): INVALID Folder [%d files]", localPath.c_str(), files.size() );
		return WRET_ERR_PARAM;
	 }
	// sort alphabetically
	std::sort( files.begin(), files.end() );
	Log(LOG_VERBOSE, ":reloadMessagesFromFolder(%s): Analysing %d elements from folder", localPath.c_str(), files.size() );

	msglist.transactionBeginWrite();
	std::vector<std::string>::const_iterator it;
	for ( it=files.begin(); it!=files.end(); it++ ) {
		AudioMessage* am = new AudioMessage();
		am->setTimeStamp( FileSystem::getTimeStampOf(*it) );
		am->setFile( *it );
		am->setAudioType( AudioMessage::AUDIO_TEXT );
		msglist.push_back(am);
	 }
	msglist.transactionEnd();

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT VGettyAMImpl::reportNewMessages( const std::string& phone_number )
 {
	if ( phone_number.length() < 5 )
		return WRET_ERR_PARAM;

	std::string execStr = this->dialScript + " " + phone_number + " report ";
	///@todo should check and forward timestamp of lase read message?

	int ret = system( execStr.c_str() );
	Log(LOG_VERBOSE, ":reportNewMessages() : Executed '%1' returned %d ", execStr.c_str(), ret );

	if ( ret == 0 )
		return WRET_OK;

	return WRET_ERR_INTERNAL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT VGettyAMImpl::dialCallAndPlay( const std::string& number, const std::string& message_rmd )
 {
	// check existence
	// convert?
	// copy to temp file
	if ( message_rmd.length() < 2 )
		return WRET_ERR_PARAM;

	std::string execStr = this->dialScript + " " + number + " play ";

	if ( message_rmd.find("/") == 0 )
		execStr += message_rmd;
	else
		execStr += this->folderMessageStored + "/" + message_rmd;

	int ret = system( execStr.c_str() );
	Log(LOG_VERBOSE, ":dialCallAndPlay() Executed '%s' returned %d", execStr.c_str(), ret );

	if ( ret == 0 )
		return WRET_OK;
	return WRET_ERR_INTERNAL;
 }

///////////////////////////////////////////////////////////////////////////////// ANSWERING MACHINE INTERFACE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PHONE INTERFACE

WRESULT VGettyAMImpl::dialCall( const std::string& phoneNumber, unsigned int  ) {
	std::string execStr = this->dialScript + " " + phoneNumber + " call ";

	int ret = system( execStr.c_str() );
	Log(LOG_VERBOSE, ":Executed '%s' returned %d", execStr.c_str(), ret );

	if ( ret == 0 )
		return WRET_OK;
	return WRET_ERR_INTERNAL;
 }

WRESULT VGettyAMImpl::hangUpCall( unsigned int  ) {
	// send udp
	return WRET_NOT_IMPLEMENTED;
 }

WRESULT VGettyAMImpl::answerCall( unsigned int  ) {
	// send udp
	return WRET_NOT_IMPLEMENTED;
 }

///////////////////////////////////////////////////////////////////////////////////////////// PHONE INTERFACE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT VGettyAMImpl::setMessageGreeting( const std::string& message_rmd )
 {
	///@todo check existence
	MutexLockerWrite mL(&this->configMux);

	std::string repo_messages = this->folderMessageStored + "/";

	std::string execRemoveOldLink = "rm " + repo_messages + "greeting.rmd";

	Log(LOG_VERBOSE, ":Removing old Link [greeting.rmd]" );

	int ret = system( execRemoveOldLink.c_str() );
	if ( ret != 0 )
		Log(LOG_WARNING, ":Error Removing Old Link?" );

	std::string execCreateNewLink = "ln " + repo_messages + message_rmd + " " + repo_messages + "greeting.rmd";

	Log(LOG_VERBOSE, ":Creating new Link: '%s'", execCreateNewLink.c_str() );

	ret = system( execCreateNewLink.c_str() );
	if ( ret != 0 ) {
		Log(LOG_WARNING, ":Error Executing '%s'", execCreateNewLink.c_str() );
		return WRET_ERR_INTERNAL;
	 }

	this->messageGreeting = message_rmd;

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT VGettyAMImpl::setDialScript( const std::string& script_path ) {
	MutexLockerWrite mL(&this->configMux);
	this->dialScript = script_path;
	return WRET_OK;
 }

WRESULT VGettyAMImpl::setCtrlScript( const std::string& script_path ) {
	MutexLockerWrite mL(&this->configMux);
	this->ctrlScript = script_path;
	return WRET_OK;
 }

WRESULT VGettyAMImpl::setBindAddressSlave( const std::string& bind_ip ) {
	MutexLockerWrite mL(&this->configMux);
	this->bindAddressSlave = bind_ip;
	return WRET_OK;
 }

WRESULT VGettyAMImpl::setBindAddressDevice( const std::string& address_port ) {
	if ( this->udpSocket == NULL ) return WRET_ERR_PARAM;
	if ( isThreadRunning() ) return WRET_ERR_ILLEGAL_USE;
	MutexLockerWrite mL(&this->configMux);
	this->bindAddressDevice = address_port;
	bool set = this->udpSocket->setAddressPort(this->bindAddressDevice);
	return WRET_OK;
 }

WRESULT VGettyAMImpl::setFolderMessageStored( const std::string& folder_path ) {
	MutexLockerWrite mL(&this->configMux);
	this->folderMessageStored = folder_path;
	return WRET_OK;
 }

WRESULT VGettyAMImpl::setFolderMessageIncoming( const std::string& folder_path ) {
	MutexLockerWrite mL(&this->configMux);
	this->folderMessageIncoming = folder_path;
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void VGettyAMImpl::setVGettyAMListener( IVGettyAMListener* Listener ) {
	MutexLockerWrite mL(&this->configMux);
	this->deviceListener = Listener;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

IVGettyAMListener* VGettyAMImpl::getVGettyAMListener() {
	MutexLockerRead mL(&this->configMux);
	return this->deviceListener;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

